<?php

namespace Dddxiu;

use Dddxiu\traits\Singleton;

/**
 * 层处理
 */
class Layer
{
    use Singleton;

    // 处理的字段
    private $field = NULL;

    // 内置规则列表
    private $rules = [];

    // 输入
    private $input = [];

    // 待处理规则列表
    private $exec_rules = [];

    // 额外参数
    private $extra = [];

    // funnel
    private $funnel;

    // 当前规则
    private $rule;

    // 元素基础类型
    const FIELD_TYPE_STR  = 'str';
    const FIELD_TYPE_NUM  = 'num';
    const FIELD_TYPE_DATE = 'date';

    // 中断
    private $interrupt;


    /**
     * 元数据
     *
     * @param array $input
     * @param array $rules
     * @param Funnel $funnel
     * @param boolean $interrupt
     * @return void
     */
    private function meta($input, $rules, $funnel, $interrupt)
    {
        $this->input     = $input;
        $this->rules     = $rules;
        $this->funnel    = $funnel;
        $this->interrupt = $interrupt;
    }

    /**
     * 执行过滤
     *
     * @param string $field
     * @param string $rule
     * @return bool
     * @throws \Exception
     */
    private function filter($field, $rule)
    {
        $this->field = $field;
        $this->rule  = $rule;

        if (is_string($rule)) {
            return $this->handleRule($rule);
        }

        if (is_array($rule) && is_callable($rule[0])) {
            throw new \Exception("回调还没有实现", 1);
        }

        if (is_callable($rule)) {
            throw new \Exception("回调还没处理", 1);
        }

        throw new \Exception("Error:" . __METHOD__, 1);
    }

    /**
     * 表达式字段处理
     * @param string $rule
     * @return bool
     * @throws \Exception
     */
    private function handleRule($rule = '')
    {
        // 1.解析表达式
        $this->exec_rules = [];
        $rules            = explode('|', $rule);
        foreach ($rules as $item) {
            $args = NULL;
            if (strpos($item, ':')) {
                $args = explode(',', substr(strstr($item, ':'), 1));
                $item = strstr($item, ':', true);
            }
            $this->exec_rules[] = [$item, $args];
        }

        // 2.处理单个规则,r或者n必须有一个值.
        // r和n必须放到第一个位置
        $idx = array_search('r', $rules);
        if ($idx !== false) {
            if ($idx !== 0) {
                $item = $this->exec_rules[$idx];
                unset($this->exec_rules[$idx]);
                array_unshift($this->exec_rules, $item);
            }
        } else {
            array_unshift($this->exec_rules, ['n', NULL]);
        }

        // 3.检查字段
        return $this->then();
    }

    /**
     * 层处理
     * @param array|null $extra
     * @return bool
     * @throws \Exception
     */
    private function then($extra = NULL)
    {
        if ($this->empty()) {
            return true;
        }

        list($exp, $exp_args) = $this->popItem();
        $exec = $this->rules[$exp] ?? false;
        if ($exec === false) {
            throw new \Exception("FLAG {$exp} not exists", 1);
        }

        if (is_a($exec, 'Closure')) { // Closure
            $pass = $exec($this->input, $this->field, $this, $exp_args);
            if (is_a($pass, \Closure::class)) {
                $pass = $pass();
            }

        } elseif (class_exists($exec)) { // local rule
            $pass = $exec::valid($this->input, $this->field, $this, $exp_args);
            if ($pass instanceof \Closure) {
                $pass = $pass();
            } elseif (is_bool($pass) and $pass === false) {
                $value = $this->input[$this->field] ?? NULL;
                $this->funnel->errorRecord($this->field, $value, $exp, $exp_args);
            }

        } else { // other
            throw new \Exception("not support validator:{$exec}", 1);
        }

        if ($pass === true) {
            if (is_array($extra) && count($extra)) {
                $this->extra += $extra;
            }
            return true;
        }

        return false;
    }

    /**
     * 下一条规则
     * @param null $extra
     * @return \Closure
     */
    private function next($extra = null)
    {
        return function () use ($extra) {
            return $this->then($extra);
        };
    }

    /**
     * 是否有下一个规则
     * @return bool
     */
    private function empty()
    {
        return empty($this->exec_rules);
    }

    /**
     * 设置为空
     */
    private function setEmpty()
    {
        $this->exec_rules = [];
    }

    /**
     * 弹出一个规则
     * @return mixed
     */
    private function popItem()
    {
        return array_shift($this->exec_rules);
    }

    /**
     * 传递的额外参数
     * @param string $key 键
     * @param mixed $default 默认值
     * @return mixed|null
     */
    private function extra($key, $default = NULL)
    {
        return $this->extra[$key] ?? $default;
    }
}